Skip to content

fix(ai): fold recased gateway-namespaced tool references onto request tools - #1489

Merged
code-yeongyu merged 4 commits into
mainfrom
fix/tool-ref-recased-gateway-namespace
Sep 8, 2026
Merged

fix(ai): fold recased gateway-namespaced tool references onto request tools#1489
code-yeongyu merged 4 commits into
mainfrom
fix/tool-ref-recased-gateway-namespace

Conversation

@code-yeongyu

@code-yeongyu code-yeongyu commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Summary

Incident (2026-09-08, session 01a08016, omo 5.0.0-0.beta.48 / senpi 2026.9.7-2, claude-fable-5-1 through ccapi). A native tool search returned its tool_reference names both namespaced and recased: mcp__a4e6__Memory, mcp__a4e6__LspSymbols, mcp__a4e6__XSearch, mcp__a4e6__Eval for the request tools memory, lsp_symbols, x_search, eval (plus mcp__a4e6__cloudflare-docs_search_cloudflare_documentation, whose suffix stayed literal). Every later Anthropic request failed with Tool reference 'mcp__a4e6__Memory' not found in available tools and the session fell back to another model each turn (3 identical 400s in the transcript; the earlier attempts were masked by pool 429s, so the fallback/revert dance was a symptom, not the cause).

Fix. resolveAvailableToolName now compares names with case and _/- separators folded away after the literal and namespace-stripped literal lookups fail. The folded index is built once per request from the tools array and drops any key that two request tools share, so the fold never guesses between candidates (such a reference stays unresolved and is dropped, as before). Same seam as #1480: the repair runs against the final tools array right before the SDK call.

Refactor (first commit, no behavior change). The tool-reference integrity pass was a fork-only block (since 5ecb30463) inside the 2.7k-line anthropic-messages.ts; it now lives in packages/ai/src/api/anthropic-tool-references.ts next to anthropic-tool-pairs.ts, and its suite is split into a shared harness (anthropic-tool-reference-harness.ts) plus one file per behavior cluster (client tool_reference/tool_use integrity; native tool-search replay). Fewer lines of fork-only code left in the upstream-shared file.

Tests

  • test/anthropic-tool-reference-native-search.test.ts: recased namespaced native references fold onto memory / lsp_symbols / x_search (+ hyphenated literal kept); an ambiguous fold (x_search vs x-search) is dropped.
  • test/anthropic-tool-reference-integrity.test.ts: a recased namespaced history tool_use (mcp__a4e6__XSearch) is renamed to x_search.

Evidence (mengmotaMac, fresh worktree, bun install --frozen-lockfile)

  • RED (tests only on a1ac401): 3 failed / 10 passed - exactly the three new cases, assertion failures.
  • GREEN: 13 passed across the two files. Mutations: identity fold -> 3 failed; ambiguity guard removed -> 1 failed (the ambiguity case); restored -> 13 passed.
  • Incident replay: the real server_tool_use + tool_search_tool_result blocks from session 01a08016 through streamAnthropic with a capturing fake client -> all 8 references fold (memory, web_search, lsp_symbols, tool_search, cloudflare-docs_search_cloudflare_documentation, tool_search_tool_bm25, x_search, eval), 0 dangling.
  • biome check on the 5 touched files: clean. Root tsc --noEmit: exit 0. scripts/check-pr-changelog.mjs: PASS.
  • Full packages/ai vitest: 2594 passed / 0 failed; 1 pre-existing file load failure (test/codex-apply-patch-wire-schema.test.ts, @earendil-works/pi-tui entry unresolved in a fresh worktree - identical on the unpatched base, see fix(ai): fold gateway-namespaced Anthropic tool references onto the request's tools #1480).
  • Rebased onto fix(ai,coding-agent): deliver Anthropic error status and recover a native tool-search 400 on the same model #1487 (cf2f61564); the only conflict was httpStatusOfError landing inside the moved block, kept in anthropic-messages.ts. Re-verification on the rebased commits is recorded in the PR comments.

Trackers

  • packages/ai/src/changes.md (2026-09-08 entry) and packages/coding-agent/CHANGELOG.md [Unreleased] Fixed.

Summary by cubic

Fixes native tool-search references that the gateway hands back both namespaced and recased (mcp__a4e6__Memory for the request tool memory), which previously either hard-failed the request with Tool reference not found (forcing a weaker fallback model) or silently dropped the discovered tool. Tool-name matching now folds case and _/- separators after literal lookups fail, and only resolves a fold when exactly one request tool matches; ambiguous folds stay dropped.

Refactors

  • Moves the tool-reference integrity pass out of anthropic-messages.ts into the new anthropic-tool-references.ts module.
  • Splits the test suite into a shared harness plus one file per behavior cluster.

Written for commit 09ef716. Summary will update on new commits.

Review in cubic

The tool-reference integrity pass (demoteUnavailableToolReferences and its
helpers) was a fork-only block inside the 2.7k-line anthropic-messages.ts.
Move it to anthropic-tool-references.ts next to anthropic-tool-pairs.ts, and
split its test suite into a shared harness plus one file per behavior cluster
(client tool_reference/tool_use integrity, native tool-search replay).
No behavior change.
… tools

A native tool search replayed through a gateway can hand its references back
both namespaced and recased: `mcp__a4e6__Memory` for the request tool
`memory`, `mcp__a4e6__LspSymbols` for `lsp_symbols`. The exact-suffix fold
from #1480 left those unresolved, so the discoveries were dropped (and on the
shipped 2026.9.7-2 engine, which predates #1480, the block replayed verbatim
and every request failed with `Tool reference 'mcp__a4e6__Memory' not found
in available tools`, forcing a fallback model each turn).

Compare names with case and `_`/`-` separators folded away after the literal
lookups fail, resolving a folded key only when exactly one request tool owns
it. Same seam as #1480: the repair runs against the final tools array right
before the SDK call.

Observed 2026-09-08 in session 01a08016 (omo 5.0.0-0.beta.48, claude-fable-5-1
via ccapi); the real search-result block now folds all eight references with
none dangling.
@code-yeongyu

Copy link
Copy Markdown
Owner Author

Re-verification on the rebased commits (mengmotaMac, fresh worktree, bun install --frozen-lockfile)

Base after rebase: cf2f61564 (#1487). Conflict: #1487's httpStatusOfError landed inside the moved block; kept in anthropic-messages.ts.

commit check result
13a944481 refactor anthropic-tool-reference-{integrity,native-search} + anthropic-on-response-error 13 passed / 0 failed
13a944481 refactor root tsc --noEmit exit 0
9d98c1b77 fix same three files (verbose) 16 passed / 0 failed
9d98c1b77 fix coding-agent test/tool-search/native-anthropic + test/suite/retry-fallback-hard-error 27 passed / 0 failed
9d98c1b77 fix incident replay (real 01a08016 blocks through streamAnthropic) 8/8 references folded, 0 dangling
9d98c1b77 fix root tsc --noEmit exit 0
9d98c1b77 fix full packages/ai vitest 2597 passed / 0 failed; 1 pre-existing file load failure (codex-apply-patch-wire-schema.test.ts, pi-tui entry unresolved in a fresh worktree)
9d98c1b77 fix biome check (5 touched files) 1 format error in the test harness -> fixed in 9512a91c9

Mutation evidence (pre-rebase tree, same code): identity fold -> the 3 new cases fail; ambiguity guard removed -> the ambiguity case fails; restored -> 13 passed.

09ef7161a only names packages/ai/src/api/anthropic-messages.ts by full path in changes.md so the changelog gate covers the moved block.

@code-yeongyu
code-yeongyu merged commit 0913cbd into main Sep 8, 2026
23 checks passed
@code-yeongyu
code-yeongyu deleted the fix/tool-ref-recased-gateway-namespace branch September 8, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant